python_examples

Polyfem Examples

This is a jupyter notebook. The "real" notebook can be found here.

Some imports for plotting

In [1]:
import plotly.offline as plotly
import plotly.graph_objs as go

#Necessary for the notebook
plotly.init_notebook_mode(connected=True)

algebra

In [2]:
import numpy as np

stuff for the animation

In [3]:
import ipywidgets as widgets
from ipywidgets import interact

and finallypolyfempy

In [4]:
import polyfempy as pf

Plotting utility

This code is not particularly interesting.

It converts a tet-mesh (4 indices) into a face-based tri mesh and uses plotly Mesh3d to plot it.

In [5]:
def plot(vertices, connectivity, function):
    x = vertices[:,0]
    y = vertices[:,1]

    if vertices.shape[1] == 3:
        z = vertices[:,2]
    else:
        z = np.zeros(x.shape, dtype=x.dtype)

    if connectivity.shape[1] == 3:
        f = connectivity
    else:
        #Convert a tet-mesh into face based triangles.
        f = np.ndarray([len(connectivity)*4, 3], dtype=np.int64)
        for i in range(len(connectivity)):
            f[i*4+0] = np.array([connectivity[i][1], connectivity[i][0], connectivity[i][2]])
            f[i*4+1] = np.array([connectivity[i][0], connectivity[i][1], connectivity[i][3]])
            f[i*4+2] = np.array([connectivity[i][1], connectivity[i][2], connectivity[i][3]])
            f[i*4+3] = np.array([connectivity[i][2], connectivity[i][0], connectivity[i][3]])

    mesh = go.Mesh3d(x=x, y=y, z=z,
                     i=f[:,0], j=f[:,1], k=f[:,2],
                     intensity=function, flatshading=function is not None,
                     colorscale='Viridis',
                     contour=dict(show=True, color='#fff'),
                     hoverinfo="all")
    layout = go.Layout(scene=go.layout.Scene(aspectmode='data'))
    fig = go.Figure(data=[mesh], layout=layout)

    plotly.iplot(fig)

Plate hole example¶

This is the python version of the plate with hole example explained here.

Set the mesh path

In [6]:
mesh_path = "plane_hole.obj"

create settings

In [7]:
settings = pf.Settings()

pick linear $P_1$ elements. If the mesh would be a quad it would be $Q_1$

In [8]:
settings.discr_order = 1

normalize the mesh to be in [0,1]^2

In [9]:
settings.normalize_mesh = True

and choose Young's modulus and poisson ratio

In [10]:
settings.set_material_params("E", 210000)
settings.set_material_params("nu", 0.3)

We are use a linear material model

In [11]:
settings.tensor_formulation = pf.TensorFormulations.LinearElasticity

Next we setup the problem

In [12]:
problem = pf.GenericTensor()

sideset 1 has zero displacement in $x$

In [13]:
problem.add_dirichlet_value(1, [0, 0], [True, False])

sideset 4 has zero displacement in $y$

In [14]:
problem.add_dirichlet_value(4, [0, 0], [False, True])

sideset 3 has a force (Neumann) of [100, 0] applied

In [15]:
problem.add_neumann_value(3, [100, 0])

fianally set the problem

In [16]:
settings.set_problem(problem)

Solve!

In [17]:
solver = pf.Solver()

solver.settings(str(settings))
solver.load_mesh(mesh_path)

solver.solve()

Get the solution

In [18]:
[pts, tets, disp] = solver.get_sampled_solution()

diplace the mesh

In [19]:
vertices = pts + disp

and get the stresses

In [20]:
mises, _ = solver.get_sampled_mises_avg()

finally plot with the above code

In [21]:
plot(vertices, tets, mises)

Note that we used get_sampled_mises_avg to get the Von Mises stresses because they depend on the Jacobian of the displacement which, becase we use $P_1$ elements, is piece-wise constant. To avoid that effect in get_sampled_mises_avg the mises are averaged per vertex weighted by the area of the triangles. If you want the "real" mises just call

In [22]:
mises = solver.get_sampled_mises()
plot(vertices, tets, mises)

Torsion

Non-linear example. We want to torque a 3D bar around the $z$ direction.

The example is really similar as the one just above.

Sets the mesh and create the settings. As before

In [23]:
mesh_path = "square_beam_h.HYBRID"

settings = pf.Settings()

It is an hex-mesh so we are using $Q_1$

In [24]:
settings.discr_order = 1

In this case the mesh has already the correct size.

In [25]:
settings.normalize_mesh = False

Choose Young's modulus and poisson ratio, as before

In [26]:
settings.set_material_params("E", 200)
settings.set_material_params("nu", 0.35)

Differently from before we want a non-linear material model: NeoHookean

In [27]:
settings.tensor_formulation = pf.TensorFormulations.NeoHookean

and we want to do 5 steps of incremental loading to avoid ambiguities in the rotation

In [28]:
settings.nl_solver_rhs_steps = 5

Now we setup problem with fixed sideset, rotating an number of tours

In [29]:
problem = pf.Torsion()

sideset 5 is fixed

In [30]:
problem.fixed_boundary = 5

sideset 6 rotates

In [31]:
problem.turning_boundary = 6

around the $z$-axis (2)

In [32]:
problem.axis_coordiante = 2

by half a tour

In [33]:
problem.n_turns = 0.5

Now we choose a coarse visualization mesh

In [34]:
settings.vismesh_rel_area = 0.001

and set the problem and solve

In [35]:
settings.set_problem(problem)

#solving!
solver = pf.Solver()

solver.settings(str(settings))
solver.load_mesh(mesh_path)

solver.solve()

takes approx 1 min because it is a complicated non-linear problem!

Get solution and stesses as before

In [36]:
[pts, tets, disp] = solver.get_sampled_solution()
vertices = pts + disp
mises, _ = solver.get_sampled_mises_avg()

and plot the 3d result!

In [37]:
plot(vertices, tets, mises)

Time dependent simulation

In [38]:
n_pts = 50
extend = np.linspace(0,1,n_pts)
x, y = np.meshgrid(extend, extend, sparse=False, indexing='xy')
pts = np.column_stack((x.ravel(), y.ravel()))

Create connectivity

In [39]:
faces = np.ndarray([(n_pts-1)**2, 4],dtype=np.int32)

index = 0
for i in range(n_pts-1):
    for j in range(n_pts-1):
        faces[index, :] = np.array([
            j + i * n_pts,
            j+1 + i * n_pts,
            j+1 + (i+1) * n_pts,
            j + (i+1) * n_pts
        ])
        index = index + 1

create settings

In [40]:
settings = pf.Settings()

pick linear $Q_1$ elements.

In [41]:
settings.discr_order = 1

and choose Young's modulus and poisson ratio

In [42]:
settings.set_material_params("E", 1)
settings.set_material_params("nu", 0.3)

We are use a linear material model

In [43]:
settings.tensor_formulation = pf.TensorFormulations.LinearElasticity

For efficienty in the browser we select a coarse vis mesh

In [44]:
settings.vismesh_rel_area = 0.001

We simulate from 0 to 5s and 50 steps.

In [45]:
settings.tend = 5
settings.time_steps = 50

Next we setup the problem, this doesnt have any parameters. It will...

In [46]:
problem = pf.Gravity()

we set the problem

In [47]:
settings.set_problem(problem)

We create the solver and set the settings

In [48]:
solver = pf.Solver()
solver.settings(str(settings))

This time we are using pts and faces instead of loading from the disk

In [49]:
solver.set_mesh(pts, faces)

Solve!

In [50]:
solver.solve()

Get the solution and the mises

In [51]:
pts = solver.get_sampled_points_frames()
tris = solver.get_sampled_connectivity_frames()
disp = solver.get_sampled_solution_frames()
mises = solver.get_sampled_mises_avg_frames()

Now we are ready to do the animation

First create a figure widget

In [52]:
mesh = go.Mesh3d(x=pts[-1][:,0], y=pts[-1][:,1], z=np.zeros(pts[-1][:,0].shape, dtype=pts[0].dtype),
                 i=tris[0][:,0], j=tris[0][:,1], k=tris[0][:,2],
                 intensity=mises[0], flatshading=True,
                 colorscale='Viridis', contour=dict(show=True, color='#fff'), hoverinfo="all")
layout = go.Layout(scene=go.layout.Scene(aspectmode='data'))
fig = go.FigureWidget(data=[mesh], layout=layout)

then we need the callback

In [53]:
def on_value_change(value):
    frame_index = value['new']

    fig.data[0].x=pts[frame_index][:,0] + disp[frame_index][:,0]
    fig.data[0].y=pts[frame_index][:,1] + disp[frame_index][:,1]

    fig.data[0].intensity = mises[frame_index]

finally we create an animation widged

In [54]:
play = widgets.Play(
    value=0,
    min=0,
    max=len(mises)-1,
    step=1,
    description="Press play",
    disabled=False
)

slider = widgets.IntSlider(min=0, max=len(mises)-1)

slider.observe(on_value_change, 'value')
play.observe(play, 'value')

widgets.jslink((play, 'value'), (slider, 'value'))
widgets.VBox((widgets.HBox((play, slider)), fig))
</div>